ScrollViewReader

ScrollViewReader 组件,用于在脚本中获得对可滚动内容的编程化控制能力,使开发者能够在运行时滚动至任意视图位置,包括列表项、文本节点等。

ScrollViewReader 与 SwiftUI 的行为保持一致: 开发者通过一个回调函数获得一个 ScrollViewProxy 实例,并可以在任意时机调用 scrollTo(id) 控制滚动视图的位置。


ScrollViewProxy

ScrollViewProxy 是提供滚动控制的代理对象,由 ScrollViewReader 在渲染期间自动注入。

1interface ScrollViewProxy {
2    scrollTo: (id: string | number, anchor?: KeywordPoint | Point) => void;
3}

方法

scrollTo(id, anchor?)

滚动到某个具有指定 id 的元素。 该 id 必须在可滚动内容内存在,并通过 key 配置。

参数说明

参数 类型 必须 说明
id string number
anchor KeywordPoint Point

KeywordPoint 类型

属于字符串关键字,常用:

  • 'top'
  • 'center'
  • 'bottom'

Point 类型

用于精确控制滚动位置:

1type Point = {
2  x: number
3  y: number
4}

ScrollViewReader

ScrollViewReader 用于包裹可滚动内容,并提供一个 scrollViewProxy 以控制内部滚动。

1type ScrollViewReaderProps = {
2    children: (scrollViewProxy: ScrollViewProxy) => VirtualNode
3};
4declare const ScrollViewReader: FunctionComponent<ScrollViewReaderProps>

Props 说明

名称 类型 必须 说明
children (proxy: ScrollViewProxy) => VirtualNode 回调函数,将滚动代理传给开发者,并返回 ScrollView、List 等可滚动视图。

使用说明

  1. ScrollViewReader 必须包裹 List、ScrollView 等可滚动组件
  2. 回调中的 proxy 只在视图构建阶段提供一次,开发者可利用 useRef 保存。
  3. 支持在动画中使用,例如 withAnimation
  4. 锚点可选,不传则使用默认对齐方式。
  5. 所有 ScrollView 内部节点都可以使用 key 来作为 scrollTo 的目标。

基础示例

下面是一个完整的使用示例,包括滚动到指定元素以及使用动画的方式。

1import {
2  Button,
3  Navigation,
4  NavigationStack,
5  Script,
6  Text,
7  List,
8  ScrollViewReader,
9  ScrollView,
10  VStack,
11  useRef,
12  ScrollViewProxy,
13  withAnimation
14} from "scripting"
15
16function Item({ index }: { index: number }) {
17  return <Text>
18    Item - {index}
19  </Text>
20}
21
22function View() {
23  const dismiss = Navigation.useDismiss()
24  const proxyRef = useRef<ScrollViewProxy>()
25
26  return <NavigationStack>
27    <VStack navigationTitle="ScrollViewReader">
28
29      <ScrollViewReader>
30        {(proxy) => {
31          // 记录 proxy 实例,供按钮点击时使用
32          proxyRef.current = proxy
33
34          return <List>
35            {new Array(100).fill(0).map((_, index) =>
36              <Item
37                key={index}
38                index={index}
39              />
40            )}
41            <Text key="bottom">
42              Bottom
43            </Text>
44          </List>
45        }}
46      </ScrollViewReader>
47
48      <Button
49        title="跳转"
50        action={() => {
51          if (proxyRef.current == null) {
52            console.log("no proxy found")
53            return
54          }
55
56          const index = Math.random() * 100 | 0
57
58          withAnimation(() => {
59            proxyRef.current?.scrollTo(index, "bottom")
60            // proxyRef.current?.scrollTo("bottom", "bottom")
61          })
62        }}
63      />
64
65    </VStack>
66  </NavigationStack>
67}
68
69async function run() {
70  await Navigation.present(<View />)
71  Script.exit()
72}
73
74run()

关于 ID(key)匹配的说明

scrollTo(id) 依赖于内部节点的 key 属性。 以下配置都可作为滚动目标:

1<Text key="bottom">Bottom</Text>

key 与 SwiftUI 的 .id() 行为保持一致。


动画支持

ScrollViewReader 支持结合 withAnimation 来进行平滑滚动。例如:

1withAnimation(() => {
2  proxy.scrollTo("target", "center")
3})

在动画块中触发滚动,将获得平滑过渡。


注意事项

  1. 必须在 ScrollViewReader 回调中记录 proxy,否则外部无法访问。
  2. 必须确保目标元素存在并有唯一 id,否则无法滚到目标位置。
  3. 不支持在 ScrollViewReader 外部渲染可滚动组件
  4. 滚动行为与 SwiftUI 基本一致,包括 anchor 对齐方式。